﻿using System;
using System.Configuration;
using System.IO;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
using Microsoft.SharePoint;
using SharePoint.Contrib.App.Settings;
using SharePoint.Contrib.Diagnostics;
using SharePoint.Contrib.Extensions;

namespace SharePoint.Contrib.InversionOfControl
{   
    /// <summary>
    /// UnityServiceLocator&lt;T&gt; provides a basic implementation of a service locator with singleton behaviour.
    /// Inherit from this class to provide your own, custom, service locator with the basic behaviour that UnityServiceLocator&lt;T&gt; provides
    /// </summary>
    /// <typeparam name="T">specifies which type is returned in the .Current property</typeparam>
    /// <example>
    /// MyServiceLocator : UnityServiceLocator&lt;MyServiceLocator&gt; 
    /// </example>
    public class UnityServiceLocator<T> where T : new()
    {
       
        private IUnityContainer container;

        /// <summary>
        /// Unity uses this property to determine which config file to configure from. Override to specify a custom location for the unity.config file
        /// </summary>
        protected virtual string ConfigurationFilePath
        {
            get { return string.Empty; }
        }

        /// <summary>
        /// Called when Unity configures the container. Override to specify custom run-time configuration 
        /// </summary>
        /// <param name="container"></param>
        protected virtual void OnConfiguringAtRunTime(IUnityContainer container) {}

        /// <summary>
        /// Needed for inheritance
        /// </summary>
        protected UnityServiceLocator()
        {
            EnsureContainer();
        }

        private void EnsureInternalServices()
        {
            var DiagnosticsServiceInstance = new SPCDiagnosticsService();            

            container
                .RegisterInstance<SPCDiagnosticsService>(DiagnosticsServiceInstance, new ContainerControlledLifetimeManager());

            if (container.IsRegistered<IDiagnosticsService>() == false)
            {
                container
                    .RegisterInstance<IDiagnosticsService>(DiagnosticsServiceInstance, new ContainerControlledLifetimeManager());
            }
            if (container.IsRegistered<ISettingService>() == false)
            {
                container
                    .RegisterType<ISettingService, SPCSettingService>(new ContainerControlledLifetimeManager());
            } 

        }

        private bool HasConfigurationFile
        {
            get
            {
                return string.IsNullOrEmpty(ConfigurationFilePath) == false;
            }
        }

        private void EnsureContainer()
        {
            if (container == null)
            {
                container = new UnityContainer();
            }

            OnConfiguringAtDesignTime();
            OnConfiguringAtRunTime(container);
            EnsureInternalServices();
        }

        private void OnConfiguringAtDesignTime()
        {
            if (HasConfigurationFile)
            {
                SPSecurity.RunWithElevatedPrivileges(() =>
                {
                    if (File.Exists(ConfigurationFilePath) == false)
                    {
                        throw "UnityServiceLocator: A ConfigurationFile was not found at path '{0}'."
                                .AsException(ConfigurationFilePath);
                    }

                    var map = new ExeConfigurationFileMap { ExeConfigFilename = ConfigurationFilePath };
                    var configuration = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
                    var section = (UnityConfigurationSection)configuration.GetSection("unity");

                    container.LoadConfiguration(section);
                });
            }
        }

        public static T Current
        {
            get
            {
                return Singleton.Instance;
            }
        }

        /// <summary>
        /// Resolves a default instance of T
        /// </summary>
        /// <exception cref="System.Exception" />
        public TInstanceType InstanceOf<TInstanceType>()
        {
            if (container.IsRegistered<TInstanceType>() == false)
            {
                throw "UnityServiceLocator: The type '{0}' is not registered, please check the configuration."
                        .AsException(typeof(TInstanceType).FullName);
            }

            TInstanceType instance = default(TInstanceType);

            try
            {
                instance = container.Resolve<TInstanceType>();
            }
            catch (Exception e)
            {
                e.HandleException(Constants.Areas.SPC, Constants.Categories.Unity);

                throw "UnityServiceLocator: Failed to resolve default instance of type '{0}'" 
                        .AsException(typeof(TInstanceType).FullName);
            }

            return instance;
        }

        /// <summary>
        /// Returns a communication channel to the Diagnostics Service.
        /// </summary>
        public IDiagnosticsService DiagnosticsService
        {
            get
            {
                return InstanceOf<IDiagnosticsService>();
            }
        }

        /// <summary>
        /// Returns a communication channel to the Setting Service.
        /// </summary>
        public ISettingService SettingService
        {
            get
            {
                return InstanceOf<ISettingService>();
            }
        }

        class Singleton
        {
            static Singleton() { }
            internal static readonly T Instance = new T();
        }

    }
}
